In [82]:
from alpaca.trading.client import TradingClient
from alpaca.trading.requests import GetAssetsRequest
from alpaca.trading.enums import AssetClass
from alpaca.data.historical import CryptoHistoricalDataClient
from alpaca.data.requests import CryptoBarsRequest
from alpaca_secrets import APCA_API_KEY_ID, APCA_API_SECRET_KEY
import pandas as pd
import numpy as np
import talib
from backtesting import Strategy
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from datetime import datetime, timedelta
import inspect
trading_client = TradingClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
import multiprocessing as mp
mp.set_start_method("fork", force=True)
Data Collection¶
In [83]:
# search for US equities
search_params = GetAssetsRequest(asset_class=AssetClass.US_EQUITY)
assets = trading_client.get_all_assets(search_params)
In [84]:
def prepare_data(list_symbol, n_years = 5):
data_client = StockHistoricalDataClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
end_date = datetime(2025,7,15)
start_date = end_date - timedelta(days=n_years*365)
bars_request = StockBarsRequest(
symbol_or_symbols=list_symbol,
timeframe=TimeFrame.Minute,
# timeframe=TimeFrame.Hour,
start=start_date,
end=end_date,
adjustment="all"
)
bars = data_client.get_stock_bars(bars_request).data
dfs = {}
for sym in list_symbol:
print(f"Processing {sym}")
try:
asset = trading_client.get_asset(sym)
print(f"{asset.symbol}: Tradable = {asset.tradable}")
except Exception as e:
print(f"{sym}: Error - {e}")
candle = bars.get(sym, None)
if candle is not None:
dfs[sym] = pd.DataFrame([{k: getattr(bar, k) for k in ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'vwap']} for bar in candle])
df = dfs[sym][['timestamp', 'open', 'high', 'low', 'close', 'volume']].copy()
df.columns = ['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Timestamp'] = pd.to_datetime(df['Timestamp'])
df.set_index('Timestamp', inplace=True)
dfs[sym] = df
return dfs
In [85]:
def prepare_crypto_data(list_symbol, n_years=1):
client = CryptoHistoricalDataClient(APCA_API_KEY_ID, APCA_API_SECRET_KEY)
end = datetime(2025, 7, 15)
start = end - timedelta(days=n_years*365)
crypto_data = {}
for symbol in list_symbol:
print(f"Fetching {symbol}...")
request = CryptoBarsRequest(
symbol_or_symbols=symbol,
start=start,
end=end,
timeframe=TimeFrame.Minute,
# timeframe=TimeFrame.Hour,
adjustment="all"
)
bars = client.get_crypto_bars(request).df
if bars.empty:
print(f"No data for {symbol}")
continue
df = bars[bars.index.get_level_values(0) == symbol].droplevel(0).copy()
df.index.name = "timestamp"
df = df.rename(columns=str.lower)
df = df.reset_index()
# Supondo que df tenha ['open', 'high', 'low', 'close', 'volume']
df = df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].copy()
df.columns = ['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume']
df['Timestamp'] = pd.to_datetime(df['Timestamp'])
df.set_index('Timestamp', inplace=True)
crypto_data[symbol] = df
return crypto_data
In [86]:
def print_results(results):
print(f"Return [%]: {results['Return [%]']:.2f}")
print(f"Buy & Hold Return [%]: {results['Buy & Hold Return [%]']:.2f}")
print(f"Sharpe Ratio: {results['Sharpe Ratio']:.2f}")
print(f"# Trades: {results['_trades'].shape[0]}")
print(f"Win Rate: {results['Win Rate [%]']:.2f}%")
print(f"Max Drawdown [%]: {results['Max. Drawdown [%]']:.2f}")
print(f"Avg Trade Duration: {results['Avg. Trade Duration']}")
print(f"Best Trade [%]: {results['Best Trade [%]']:.2f}")
print(f"Worst Trade [%]: {results['Worst Trade [%]']:.2f}")
print("="*60)
ETFs Data¶
In [87]:
list_symbol_ = ["SPY","QQQ","IWM","DIA","XLF","XLK","GLD","IAU","TLT","HYG",]
etfs_close_data={}
for sym in list_symbol_:
etfs_close_data[sym] = pd.read_csv(
f"etfs_{sym.split()[0]}.csv", index_col=0, parse_dates=True)
# for download of data
# etfs_close_data = prepare_data(list_symbol_, n_years = 1)
etfs_close_data.keys()
Out[87]:
dict_keys(['SPY', 'QQQ', 'IWM', 'DIA', 'XLF', 'XLK', 'GLD', 'IAU', 'TLT', 'HYG'])
Equities Data¶
In [88]:
eqt_symbol_ = ["AAPL","MSFT","GOOG","META","TSLA"]
eqt_close_data={}
for sym in eqt_symbol_:
eqt_close_data[sym] = pd.read_csv(
f"eqt_{sym.split()[0]}.csv", index_col=0, parse_dates=True)
# for download of data
# eqt_close_data = prepare_data(list_symbol_, n_years = 1)
eqt_close_data.keys()
Out[88]:
dict_keys(['AAPL', 'MSFT', 'GOOG', 'META', 'TSLA'])
Crypto Data¶
In [89]:
crypto_symbols = ["BTC/USD", "ETH/USD", "SOL/USD", "XRP/USD"]
crypto_close_data={}
for sym in crypto_symbols:
crypto_close_data[sym] = pd.read_csv(
f"crypto_{sym.split('/')[0]}.csv", index_col=0, parse_dates=True)
# for download of data
# crypto_close_data = prepare_crypto_data(crypto_symbols, n_years=1)
crypto_close_data.keys()
Out[89]:
dict_keys(['BTC/USD', 'ETH/USD', 'SOL/USD', 'XRP/USD'])
Mean Reversion Strat¶
In [90]:
class ZScoreMeanReversion(Strategy):
window = 50
threshold = 1
def init(self):
close = self.data.Close
self.ma = self.I(lambda x: pd.Series(x).rolling(self.window).mean(), close)
self.std = self.I(lambda x: pd.Series(x).rolling(self.window).std(), close)
def next(self):
if len(self.ma) < 2:
return
z = (self.data.Close[-1] - self.ma[-1]) / self.std[-1]
if z > self.threshold:
self.position.close()
self.sell(size =1)
elif z < -self.threshold:
self.position.close()
self.buy(size =1)
In [ ]:
class ZScoreMeanReversion_StopLoss(Strategy):
# window = 20
# threshold = 1.25
# stop_loss_pct = 0.015 # 1% stop loss
window = 50
threshold = 1
stop_loss_pct = 0.01 # 1% stop loss
def init(self):
close = self.data.Close
self.ma = self.I(lambda x: pd.Series(x).rolling(self.window).mean(), close, name='ma')
self.std = self.I(lambda x: pd.Series(x).rolling(self.window).std(), close, name='std')
self.z = self.I(lambda x, ma, std: (x - ma) / std, close, self.ma, self.std, name='z')
self.entry_price = None
def next(self):
if len(self.ma) < 2:
return
z = self.z[-1]
price = self.data.Close[-1]
if not self.position:
if z < -self.threshold:
self.entry_price = price
self.buy(size=100)
elif z > self.threshold:
self.entry_price = price
self.sell(size=100)
else:
stop_loss_hit = False
if self.position.is_long:
stop_price = self.entry_price * (1 - self.stop_loss_pct)
if price <= stop_price:
stop_loss_hit = True
elif z > 0:
self.position.close()
elif self.position.is_short:
stop_price = self.entry_price * (1 + self.stop_loss_pct)
if price >= stop_price:
stop_loss_hit = True
elif z < 0:
self.position.close()
if stop_loss_hit:
self.position.close()
In [92]:
class ZScoreMeanReversionImproved(Strategy):
window = 50
threshold = 1
stop_loss_pct = 0.01
atr_window = 14
cooldown_period = 5
def init(self):
close = self.data.Close
self.ma = self.I(lambda x: pd.Series(x).rolling(self.window).mean(), close)
self.std = self.I(lambda x: pd.Series(x).rolling(self.window).std(), close)
self.z = self.I(lambda x, ma, std: (x - ma) / std, close, self.ma, self.std)
self.atr = self.I(self.compute_atr, self.data.High, self.data.Low, self.data.Close)
self.entry_price = None
self.stop_triggered_at = -100
def compute_atr(self, high, low, close):
high = pd.Series(high)
low = pd.Series(low)
close = pd.Series(close)
prev_close = close.shift(1)
tr = pd.concat([
high - low,
(high - prev_close).abs(),
(low - prev_close).abs()
], axis=1).max(axis=1)
atr = tr.rolling(self.atr_window).mean()
return atr.values
def next(self):
if len(self.ma) < 2:
return
i = len(self.data.Close) - 1
price = self.data.Close[-1]
z = self.z[-1]
atr = self.atr[-1]
if i - self.stop_triggered_at < self.cooldown_period:
return
# STOP LOSS
if self.position:
stop_price = self.entry_price * (1 - self.stop_loss_pct) if self.position.is_long else self.entry_price * (1 + self.stop_loss_pct)
if self.position.is_long:
if price >= self.ma[-1] or price <= stop_price:
self.position.close()
if price <= stop_price:
self.stop_triggered_at = i
elif self.position.is_short:
if price <= self.ma[-1] or price >= stop_price:
self.position.close()
if price >= stop_price:
self.stop_triggered_at = i
if not self.position and atr < 0.02 * price:
if z < -self.threshold:
self.entry_price = price
self.buy(size=1)
elif z > self.threshold:
self.entry_price = price
self.sell(size=1)
In [93]:
class RSIMeanReversion(Strategy):
rsi_window = 14
lower_thresh = 30
upper_thresh = 70
def init(self):
close = pd.Series(self.data.Close)
self.rsi = self.I(self.calculate_rsi, close)
def calculate_rsi(self, series):
if len(series) < self.rsi_window:
return pd.Series([np.nan] * len(series))
delta = pd.Series(series).diff()
up = delta.clip(lower=0)
down = -delta.clip(upper=0)
roll_up = up.ewm(span=self.rsi_window, adjust=False).mean()
roll_down = down.ewm(span=self.rsi_window, adjust=False).mean()
rs = roll_up / roll_down
rsi = 100 - (100 / (1 + rs))
return rsi.fillna(0)
def next(self):
if len(self.rsi) == 0 or np.isnan(self.rsi[-1]):
return
if self.rsi[-1] < self.lower_thresh:
self.position.close()
self.buy(size=1)
elif self.rsi[-1] > self.upper_thresh:
self.position.close()
self.sell(size=1)
In [94]:
class OUProcessReversion(Strategy):
window = 100
entry_threshold = 1.5
exit_threshold = 0.5
def init(self):
close = self.data.Close
self.mean = self.I(lambda x: pd.Series(x).ewm(span=self.window).mean(), close)
self.std = self.I(lambda x: pd.Series(x).ewm(span=self.window).std(), close)
def next(self):
z_score = (self.data.Close[-1] - self.mean[-1]) / self.std[-1]
if self.position.is_long and abs(z_score) < self.exit_threshold:
self.position.close()
elif self.position.is_short and abs(z_score) < self.exit_threshold:
self.position.close()
elif z_score > self.entry_threshold:
self.position.close()
self.sell(size=1)
elif z_score < -self.entry_threshold:
self.position.close()
self.buy(size=1)
In [95]:
class MRATStrategy(Strategy):
short_window = 21
long_window = 200
z_threshold = 1.0
def init(self):
close = self.data.Close
self.ma_short = self.I(lambda x: pd.Series(x).rolling(self.short_window).mean(), close, name='ma_short')
self.ma_long = self.I(lambda x: pd.Series(x).rolling(self.long_window).mean(), close, name='ma_long')
def next(self):
if len(self.ma_short) < self.long_window or len(self.ma_long) < self.long_window:
return
# MRAT = MA21 / MA200
mrat = self.ma_short[-1] / self.ma_long[-1]
# Histórico de MRAT para cálculo de desvio
hist_mrat = np.array(self.ma_short[-self.long_window:] / self.ma_long[-self.long_window:])
mean_mrat = np.mean(hist_mrat)
std_mrat = np.std(hist_mrat)
z_score = (mrat - mean_mrat) / std_mrat
if abs(z_score) < 0.3 and self.position:
self.position.close()
# Long
elif z_score > self.z_threshold:
if self.position.is_short:
self.position.close()
if not self.position.is_long:
self.buy(size=1)
# Short
elif z_score < -self.z_threshold:
if self.position.is_long:
self.position.close()
if not self.position.is_short:
self.sell(size=1)
Backtesting¶
In [96]:
def run_strategies(df, strategies, commission = 0.02, plots=False):
df_bt = df.reset_index()[['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume']].copy()
df_bt['Timestamp'] = pd.to_datetime(df_bt['Timestamp'])
df_bt.set_index('Timestamp', inplace=True)
for strategy in strategies:
print("="*60)
print(f"Running strategy: {strategy.__name__}")
print("-"*60)
bt = Backtest(df_bt, strategy, cash=100_000, commission=.01, exclusive_orders=True)
results = bt.run()
if plots:
bt.plot()
if 'all_results' not in locals():
all_results = []
result_dict = {
"Strategy": strategy.__name__,
"Return [%]": results['Return [%]'],
"Buy & Hold Return [%]": results['Buy & Hold Return [%]'],
"Sharpe Ratio": results['Sharpe Ratio'],
"# Trades": results['_trades'].shape[0],
"Win Rate [%]": results['Win Rate [%]'],
"Max Drawdown [%]": results['Max. Drawdown [%]'],
"Avg Trade Duration": results['Avg. Trade Duration'],
"Best Trade [%]": results['Best Trade [%]'],
"Worst Trade [%]": results['Worst Trade [%]'],
}
all_results.append(result_dict)
# Após rodar todos, cria o DataFrame
results_df = pd.DataFrame(all_results)
return results_df
In [97]:
df = etfs_close_data["IAU"]
df
strategies = [ZScoreMeanReversion, ZScoreMeanReversion_StopLoss,ZScoreMeanReversionImproved,
RSIMeanReversion, OUProcessReversion, MRATStrategy]
all_results = run_strategies(df, strategies, commission =0)
============================================================ Running strategy: ZScoreMeanReversion ------------------------------------------------------------ ============================================================ Running strategy: ZScoreMeanReversion_StopLoss ------------------------------------------------------------ ============================================================ Running strategy: ZScoreMeanReversionImproved ------------------------------------------------------------ ============================================================ Running strategy: RSIMeanReversion ------------------------------------------------------------ ============================================================ Running strategy: OUProcessReversion ------------------------------------------------------------ ============================================================ Running strategy: MRATStrategy ------------------------------------------------------------
Selection best strats on simple parameters¶
In [98]:
all_results.sort_values(by='Return [%]', ascending=False).head(3)
Out[98]:
| Strategy | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|
| 5 | MRATStrategy | -0.828277 | 37.095021 | -31.034327 | 784 | 40.178571 | -0.828407 | 0 days 08:30:00 | 3.525488 | -1.811448 |
| 2 | ZScoreMeanReversionImproved | -4.331412 | 38.176638 | -42.821752 | 3976 | 66.825956 | -4.331412 | 0 days 01:43:00 | 2.063690 | -2.066251 |
| 4 | OUProcessReversion | -13.072290 | 38.601891 | -33.679329 | 11997 | 39.568225 | -13.072290 | 0 days 00:21:00 | 2.067752 | -2.737069 |
In [99]:
all_results.sort_values(by='Win Rate [%]', ascending=False).head(3)
Out[99]:
| Strategy | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | ZScoreMeanReversion_StopLoss | -90.460280 | 38.176638 | -25.904086 | 947 | 68.743400 | -90.460280 | 0 days 01:54:00 | 0.736377 | -1.384920 |
| 2 | ZScoreMeanReversionImproved | -4.331412 | 38.176638 | -42.821752 | 3976 | 66.825956 | -4.331412 | 0 days 01:43:00 | 2.063690 | -2.066251 |
| 5 | MRATStrategy | -0.828277 | 37.095021 | -31.034327 | 784 | 40.178571 | -0.828407 | 0 days 08:30:00 | 3.525488 | -1.811448 |
In [100]:
all_results.sort_values(by='Sharpe Ratio', ascending=False).head(3)
Out[100]:
| Strategy | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | ZScoreMeanReversion_StopLoss | -90.460280 | 38.176638 | -25.904086 | 947 | 68.743400 | -90.460280 | 0 days 01:54:00 | 0.736377 | -1.384920 |
| 5 | MRATStrategy | -0.828277 | 37.095021 | -31.034327 | 784 | 40.178571 | -0.828407 | 0 days 08:30:00 | 3.525488 | -1.811448 |
| 4 | OUProcessReversion | -13.072290 | 38.601891 | -33.679329 | 11997 | 39.568225 | -13.072290 | 0 days 00:21:00 | 2.067752 | -2.737069 |
optmizing parameters for each top strats¶
MRATStrategy¶
In [101]:
bt = Backtest(df, MRATStrategy, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
short_window=range(10, 50, 10),
long_window=range(100, 250, 25),
z_threshold=[0.5, 0.75, 1.0, 1.5],
maximize='Sharpe Ratio',
constraint=lambda p: p.short_window < p.long_window
)
print(results._strategy)
print_results(results)
bt.plot()
MRATStrategy(short_window=20,long_window=200,z_threshold=1.0) Return [%]: 0.02 Buy & Hold Return [%]: 37.10 Sharpe Ratio: 2.99 # Trades: 795 Win Rate: 40.88% Max Drawdown [%]: -0.00 Avg Trade Duration: 0 days 08:18:00 Best Trade [%]: 3.53 Worst Trade [%]: -1.81 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[101]:
GridPlot(
id = 'p9514', …)
In [102]:
bt = Backtest(df, MRATStrategy, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
short_window=range(10, 50, 10),
long_window=range(100, 250, 25),
z_threshold=[0.5, 0.75, 1.0, 1.5],
maximize='Win Rate [%]',
constraint=lambda p: p.short_window < p.long_window
)
print(results._strategy)
print_results(results)
bt.plot()
MRATStrategy(short_window=40,long_window=100,z_threshold=0.5) Return [%]: 0.01 Buy & Hold Return [%]: 37.72 Sharpe Ratio: 1.58 # Trades: 1498 Win Rate: 45.33% Max Drawdown [%]: -0.01 Avg Trade Duration: 0 days 05:00:00 Best Trade [%]: 3.59 Worst Trade [%]: -1.77 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[102]:
GridPlot(
id = 'p9891', …)
OUProcessReversion¶
In [103]:
bt = Backtest(df, OUProcessReversion, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
window=range(20, 200, 20),
entry_threshold=[1.0, 1.25, 1.5, 1.75, 2.0],
exit_threshold=[0.25, 0.5, 0.75, 1.0],
maximize='Sharpe Ratio'
)
print(results._strategy)
print_results(results)
bt.plot()
OUProcessReversion(window=20,entry_threshold=1.25,exit_threshold=0.25) Return [%]: 0.01 Buy & Hold Return [%]: 38.60 Sharpe Ratio: 1.48 # Trades: 19061 Win Rate: 40.01% Max Drawdown [%]: -0.00 Avg Trade Duration: 0 days 00:18:00 Best Trade [%]: 2.70 Worst Trade [%]: -1.64 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[103]:
GridPlot(
id = 'p10308', …)
In [104]:
bt = Backtest(df, OUProcessReversion, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
window=range(20, 200, 20),
entry_threshold=[1.0, 1.25, 1.5, 1.75, 2.0],
exit_threshold=[0.25, 0.5, 0.75, 1.0],
maximize='Win Rate [%]',
)
print(results._strategy)
print_results(results)
bt.plot()
OUProcessReversion(window=20,entry_threshold=2.0,exit_threshold=0.25) Return [%]: 0.00 Buy & Hold Return [%]: 38.60 Sharpe Ratio: 0.59 # Trades: 1250 Win Rate: 51.68% Max Drawdown [%]: -0.01 Avg Trade Duration: 0 days 02:21:00 Best Trade [%]: 1.21 Worst Trade [%]: -2.65 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[104]:
GridPlot(
id = 'p10725', …)
ZScoreMeanReversion_StopLoss¶
In [ ]:
bt = Backtest(df, ZScoreMeanReversion_StopLoss, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
window=range(20, 120, 10),
threshold=[0.75, 1.0, 1.25, 1.5, 2.0],
stop_loss_pct=[0.005, 0.01, 0.015, 0.02],
maximize='Sharpe Ratio'
)
print(results._strategy)
print_results(results)
bt.plot()
ZScoreMeanReversion_StopLoss(window=20,threshold=1.25,stop_loss_pct=0.015) Return [%]: 1.43 Buy & Hold Return [%]: 37.98 Sharpe Ratio: 2.10 # Trades: 7234 Win Rate: 63.84% Max Drawdown [%]: -0.52 Avg Trade Duration: 0 days 00:52:00 Best Trade [%]: 2.70 Worst Trade [%]: -1.59 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[ ]:
GridPlot(
id = 'p5607', …)
In [ ]:
bt = Backtest(df, ZScoreMeanReversion_StopLoss, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
window=range(20, 120, 10),
threshold=[0.75, 1.0, 1.25, 1.5, 2.0],
stop_loss_pct=[0.005, 0.01, 0.015, 0.02],
maximize='Sharpe Ratio',
)
print(results._strategy)
print_results(results)
bt.plot()
ZScoreMeanReversion_StopLoss(window=20,threshold=1.25,stop_loss_pct=0.015) Return [%]: 1.43 Buy & Hold Return [%]: 37.98 Sharpe Ratio: 2.10 # Trades: 7234 Win Rate: 63.84% Max Drawdown [%]: -0.52 Avg Trade Duration: 0 days 00:52:00 Best Trade [%]: 2.70 Worst Trade [%]: -1.59 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[ ]:
GridPlot(
id = 'p6074', …)
ZScoreMeanReversionImproved¶
In [ ]:
bt = Backtest(df, ZScoreMeanReversionImproved, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
window=range(20, 120, 10),
threshold=[0.75, 1.0, 1.25, 1.5, 2.0],
stop_loss_pct=[0.005, 0.01, 0.015, 0.02],
maximize='Sharpe Ratio'
)
print(results._strategy)
print_results(results)
bt.plot()
ZScoreMeanReversionImproved(window=20,threshold=1.25,stop_loss_pct=0.01) Return [%]: 0.15 Buy & Hold Return [%]: 37.98 Sharpe Ratio: 2.24 # Trades: 7252 Win Rate: 63.87% Max Drawdown [%]: -0.05 Avg Trade Duration: 0 days 00:51:00 Best Trade [%]: 2.70 Worst Trade [%]: -1.41 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[ ]:
GridPlot(
id = 'p6591', …)
In [ ]:
bt = Backtest(df, ZScoreMeanReversionImproved, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.optimize(
window=range(20, 120, 10),
threshold=[0.75, 1.0, 1.25, 1.5, 2.0],
stop_loss_pct=[0.005, 0.01, 0.015, 0.02],
maximize='Sharpe Ratio',
)
print(results._strategy)
print_results(results)
bt.plot()
ZScoreMeanReversionImproved(window=20,threshold=1.25,stop_loss_pct=0.01) Return [%]: 0.15 Buy & Hold Return [%]: 37.98 Sharpe Ratio: 2.24 # Trades: 7252 Win Rate: 63.87% Max Drawdown [%]: -0.05 Avg Trade Duration: 0 days 00:51:00 Best Trade [%]: 2.70 Worst Trade [%]: -1.41 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[ ]:
GridPlot(
id = 'p7109', …)
Summary¶
In [113]:
data = [
["MRATStrategy", "short=20, long=200, z=1.0", 0.22, 37.10, 2.98, 795, 40.88, -0.04, "0 days 08:18:00", 3.53, -1.81],
["MRATStrategy", "short=40, long=100, z=0.5", 0.14, 37.72, 1.58, 1498, 45.33, -0.08, "0 days 05:00:00", 3.59, -1.77],
["OUProcessReversion", "window=20, entry=1.25, exit=0.25", 0.01, 38.60, 1.48, 19061, 40.01, -0.00, "0 days 00:18:00", 2.70, -1.64],
["OUProcessReversion", "window=20, entry=2.0, exit=0.25", 0.00, 38.60, 0.59, 1250, 51.68, -0.01, "0 days 02:21:00", 1.21, -2.65],
["ZScoreMeanReversion_StopLoss", "window=20, threshold=1.25, sl=0.015", 1.43, 37.98, 2.10, 7234, 63.84, -0.52, "0 days 00:52:00", 2.70, -1.59],
["ZScoreMeanReversion_StopLoss", "window=20, threshold=1.25, sl=0.015", 1.43, 37.98, 2.10, 7234, 63.84, -0.52, "0 days 00:52:00", 2.70, -1.59],
["ZScoreMeanReversionImproved", "window=20, threshold=1.25, sl=0.01", 0.15, 37.98, 2.24, 7252, 63.87, -0.05, "0 days 00:51:00", 2.70, -1.41],
["ZScoreMeanReversionImproved", "window=20, threshold=1.25, sl=0.01", 0.15, 37.98, 2.24, 7252, 63.87, -0.05, "0 days 00:51:00", 2.70, -1.41]
]
columns = [
"Strategy", "Params", "Return [%]", "Buy & Hold Return [%]", "Sharpe Ratio",
"# Trades", "Win Rate [%]", "Max Drawdown [%]", "Avg Trade Duration",
"Best Trade [%]", "Worst Trade [%]"
]
summary = pd.DataFrame(data, columns=columns)
summary
Out[113]:
| Strategy | Params | Return [%] | Buy & Hold Return [%] | Sharpe Ratio | # Trades | Win Rate [%] | Max Drawdown [%] | Avg Trade Duration | Best Trade [%] | Worst Trade [%] | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | MRATStrategy | short=20, long=200, z=1.0 | 0.22 | 37.10 | 2.98 | 795 | 40.88 | -0.04 | 0 days 08:18:00 | 3.53 | -1.81 |
| 1 | MRATStrategy | short=40, long=100, z=0.5 | 0.14 | 37.72 | 1.58 | 1498 | 45.33 | -0.08 | 0 days 05:00:00 | 3.59 | -1.77 |
| 2 | OUProcessReversion | window=20, entry=1.25, exit=0.25 | 0.01 | 38.60 | 1.48 | 19061 | 40.01 | -0.00 | 0 days 00:18:00 | 2.70 | -1.64 |
| 3 | OUProcessReversion | window=20, entry=2.0, exit=0.25 | 0.00 | 38.60 | 0.59 | 1250 | 51.68 | -0.01 | 0 days 02:21:00 | 1.21 | -2.65 |
| 4 | ZScoreMeanReversion_StopLoss | window=20, threshold=1.25, sl=0.015 | 1.43 | 37.98 | 2.10 | 7234 | 63.84 | -0.52 | 0 days 00:52:00 | 2.70 | -1.59 |
| 5 | ZScoreMeanReversion_StopLoss | window=20, threshold=1.25, sl=0.015 | 1.43 | 37.98 | 2.10 | 7234 | 63.84 | -0.52 | 0 days 00:52:00 | 2.70 | -1.59 |
| 6 | ZScoreMeanReversionImproved | window=20, threshold=1.25, sl=0.01 | 0.15 | 37.98 | 2.24 | 7252 | 63.87 | -0.05 | 0 days 00:51:00 | 2.70 | -1.41 |
| 7 | ZScoreMeanReversionImproved | window=20, threshold=1.25, sl=0.01 | 0.15 | 37.98 | 2.24 | 7252 | 63.87 | -0.05 | 0 days 00:51:00 | 2.70 | -1.41 |
In [117]:
summary.to_csv("strat_mean_rev_summary.csv",index=False)
df_meanrev = pd.read_csv("strat_mean_rev_summary.csv")
df_meanrev.to_latex("results_meanrev.csv_tex", index=False, longtable=True, escape=False)
In [ ]:
bt = Backtest(df, ZScoreMeanReversion_StopLoss, cash=100_000, commission=.00, exclusive_orders=True)
results = bt.run()
print(results._strategy)
print_results(results)
bt.plot()
# bt.plot(filename='final_image_trend_tsla.html')
ZScoreMeanReversion_StopLoss Return [%]: 1.43 Buy & Hold Return [%]: 37.98 Sharpe Ratio: 2.10 # Trades: 7234 Win Rate: 63.84% Max Drawdown [%]: -0.52 Avg Trade Duration: 0 days 00:52:00 Best Trade [%]: 2.70 Worst Trade [%]: -1.59 ============================================================
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/backtesting/_plotting.py:141: UserWarning: Data contains too many candlesticks to plot; downsampling to '1h'. See `Backtest.plot(resample=...)`
warnings.warn(f"Data contains too many candlesticks to plot; downsampling to {freq!r}. "
/Users/guilistocco/Trabalho_Final/trading_strategy/trading_env/lib/python3.10/site-packages/bokeh/util/serialization.py:242: UserWarning: no explicit representation of timezones available for np.datetime64
return convert(array.astype("datetime64[us]"))
Out[ ]:
GridPlot(
id = 'p12064', …)
In [ ]: